package com.xinziruo.richclient.handler;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.solr.common.SolrInputDocument;
import org.codehaus.jackson.JsonNode;
import org.htmlcleaner.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.xinziruo.richclient.engine.CrawlEngine;
import com.xinziruo.richclient.engine.CrawlResponse;
import com.xinziruo.richclient.engine.IntrudeCrawlEngine;
import com.xinziruo.richclient.engine.Param;
import com.xinziruo.richclient.engine.ParamType;
import com.xinziruo.richclient.engine.PayloadSource;
import com.xinziruo.richclient.engine.impl.HttpClientCrawlEngine;
import com.xinziruo.richclient.engine.impl.NumbersPayloadSource;
import com.xinziruo.richclient.engine.impl.StringListPayloadSource;
import com.xinziruo.richclient.util.JsonUtils;
import com.xinziruo.richclient.util.OCRUtils;
import com.xinziruo.richclient.util.ShortLinkUtils;
import com.xinziruo.richclient.util.XPathUtils;
import com.xinziruo.richclient.support.command.Command;
import com.xinziruo.richclient.support.persist.MysqlDBService;
import com.xinziruo.richclient.support.persist.ProductSource;

/**
 * 将采集的商品写入mysqlDB
 * 
 * @author loudyn
 * @modify ajian
 * 
 * 
 */
public class ClientCommandProtocolHandlerForMysqlDB extends IoHandlerAdapter {
	private static Logger LOGGER = LoggerFactory
			.getLogger(ClientCommandProtocolHandlerForMysqlDB.class);

	private MysqlDBService getMysqlDBService = new MysqlDBService();

	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		LOGGER.error(cause.getMessage());
		System.exit(0);
	}

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {

		Command comm = (Command) message;
		JsonNode json = JsonUtils.fromJsonString(new String(comm.content(),
				"utf-8"), JsonNode.class);
		System.out.println("recv server crawl command : " + json.path("name"));
		List<Param> params = readParams(json);
		String host = json.path("enterUrl").getTextValue();
		if (params.isEmpty()) {
			try {
				CrawlEngine engine = new HttpClientCrawlEngine(host);
				CrawlResponse response = engine.issueRequest();
				Document doc = CrawlParser.parse(response);
				handleDocument(doc, json);
			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			}
			return;
		}

		PayloadSource payload = readPayload(json);
		IntrudeCrawlEngine engine = new HttpClientCrawlEngine(host, params,
				payload);
		while (engine.hasNextRequest()) {
			try {

				CrawlResponse response = engine.issueRequest();
				Document doc = CrawlParser.parse(response);
				handleDocument(doc, json);

			} catch (Exception e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
	}

	protected void handleDocument(Document doc, JsonNode json) throws Exception {

		NodeList items = XPathUtils.evalAsNodeList(doc,
				json.path("itemsExpression").getTextValue());
		if (items.getLength() <= 0) {
			return;
		}
		boolean isFollowItem = json.path("followingItem").getBooleanValue();
		if (isFollowItem) {
			followItem(items, json);
			return;
		}
		nofollowItem(items, json);
	}

	protected void nofollowItem(NodeList items, JsonNode json) {

		String sitename = json.path("name").getTextValue().split("-")[0];
		String classify = json.path("classify").getTextValue();
		String project = json.path("project").getTextValue();
		String enterUrl = json.path("enterUrl").getTextValue();
		for (int i = 0; i < items.getLength(); i++) {
			Node item = items.item(i);
			Map<String, Object> values = CrawlParser
					.parse(enterUrl, item, json);
			try {
				ProductSource productSource = getProductSource(values,
						sitename, classify, project);
				submitMysqlDB(productSource);
			} catch (RuntimeException e) {
				// ignore this exception
			}

		}
	}

	private void submitMysqlDB(ProductSource productSource) {

		try {
			ProductSource oldProductSource = getMysqlDBService
					.queryById(productSource.getId());
			if (null != oldProductSource) {
				productSource.setOldprice(oldProductSource.getPrice());
				getMysqlDBService.update(productSource);
			} else {
				getMysqlDBService.insert(productSource);
			}
		} catch (Exception e) {
			LOGGER.error("error with the sql:", e);
		}
	}

	protected void followItem(NodeList items, JsonNode json) {

		String sitename = json.path("name").getTextValue().split("-")[0];
		String classify = json.path("classify").getTextValue();
		String project = json.path("project").getTextValue();
		String enterUrl = json.path("enterUrl").getTextValue();
		for (int i = 0; i < items.getLength(); i++) {
			try {
				Node item = items.item(i);
				String locExpression = findLocExpression(json);
				String loc = XPathUtils.evalAsString(item, locExpression);
				loc = Utils.fullUrl(enterUrl, filterLocValue(loc, json));

				CrawlEngine engine = new HttpClientCrawlEngine(loc);
				CrawlResponse response = engine.issueRequest();
				Document itemDoc = CrawlParser.parse(response);

				Map<String, Object> values = CrawlParser.parse(loc, itemDoc,
						json);
				replaceLocValue(values, loc);
				ProductSource productSource = getProductSource(values,
						sitename, classify, project);
				submitMysqlDB(productSource);
			} catch (Exception e) {
				LOGGER.warn("error ocurr when follow item loc", e);
			}

		}
	}

	private String filterLocValue(String loc, JsonNode json) {
		JsonNode node = findLocJsonNode(json);

		if (null != node) {

			for (JsonNode stringFilter : node.path("stringFilterConfigs")) {

				String regex = stringFilter.path("regex").getTextValue();
				String replacement = stringFilter.path("replacement")
						.getTextValue();
				loc = loc.replaceAll(regex, replacement);
			}

		}

		return loc;
	}

	private JsonNode findLocJsonNode(JsonNode json) {
		for (JsonNode node : json.path("parseExpressions")) {
			String name = node.path("name").getTextValue();

			if (StringUtils.equalsIgnoreCase("loc", name)) {
				return node;
			}
		}

		return null;
	}

	private String findLocExpression(JsonNode json) {
		JsonNode locNode = findLocJsonNode(json);
		if (null != locNode) {
			return locNode.path("expression").getTextValue();
		}

		return null;
	}

	private void replaceLocValue(Map<String, Object> values, String loc) {
		if (values.containsKey("loc")) {
			values.remove("loc");
		}

		values.put("loc", loc);
	}

	private ProductSource getProductSource(Map<String, Object> values,
			String sitename, String classify, String project) {
		String id = "";
		String price = "0.00";
		ProductSource productSource = new ProductSource();
		for (Entry<String, Object> entry : values.entrySet()) {
			String fieldName = entry.getKey();
			Object fieldValue = entry.getValue();
			if (StringUtils.equalsIgnoreCase("price", fieldName)) {
				if(fieldValue.toString().startsWith("http://")){
					price = OCRUtils.fromUrl(fieldValue.toString()).ocr();
					if(price.startsWith("51")){
						if(Double.valueOf(price)>10000){
							price = price.replaceAll("^51", "");
						}
					}
					price = price.replaceAll("[^0-9.]*", "");
					if(!price.matches("[0-9]+(?:\\.[0-9]*)?")){
						price = "0.00";
					}
				}else if(fieldValue.toString().matches("[0-9]+(?:\\.[0-9]*)?")) {
					price = fieldValue.toString();
				} else {
					price = "0.00";
				}
				continue;
			} else if (StringUtils.equalsIgnoreCase("loc", fieldName)) {
				productSource.setLink(getFitString(fieldValue));
				id = ShortLinkUtils.shortUrl(getFitString(fieldValue))[0];
				productSource.setId(id);
			} else if (StringUtils.equalsIgnoreCase("title", fieldName)) {
				productSource.setTitle(getFitString(fieldValue));
			}else if (StringUtils.equalsIgnoreCase("image", fieldName)) {
				productSource.setImageUrl(getFitString(fieldValue));
			}
		}
		long current = System.currentTimeMillis();
		productSource.setCreateTime(current).setUpdateTime(current)
				.setSource(sitename).setPrice(Double.valueOf(price));
		return productSource;
	}
	
	/**
	 * 判断是否是list类型，如果是则取第一个值
	 */
	public static String getFitString(Object fieldValue){
		if(fieldValue instanceof List<?>){
			return ((List<?>)fieldValue).get(0).toString().trim();
		}else{
			return fieldValue.toString().trim();
		}
	}
	public static class DocumentHelper {
		public static String getString(SolrInputDocument solrInputDocument,
				String key, String defaultValue) {
			if (!solrInputDocument.containsKey(key)
					|| "".equals(solrInputDocument.getFieldValue(key))) {
				return defaultValue;
			}
			return (String) solrInputDocument.getFieldValue(key);
		}
	}

	public enum DatePopulator {

		ME(Arrays.asList(new SimpleDateFormat("yyyy-MM-dd"),
				new SimpleDateFormat("yyyyMMdd"), new SimpleDateFormat(
						"yyyy/MM/dd"))) {
			public long populate(String dateString) {

				for (SimpleDateFormat sdf : getFormats()) {
					try {

						return sdf.parse(dateString).getTime();
					} catch (Exception ingore) {
						// ingore it
					}
				}

				return 0;
			}
		};

		private final List<SimpleDateFormat> formats;

		private DatePopulator(List<SimpleDateFormat> formats) {
			this.formats = formats;
		}

		protected List<SimpleDateFormat> getFormats() {
			return formats;
		}

		public abstract long populate(String dateString);
	}

	private List<Param> readParams(JsonNode json) {
		List<Param> result = new ArrayList<Param>();

		for (JsonNode node : json.path("params")) {

			Param param = new Param()
					.setName(node.path("name").getTextValue())
					.setEncode(node.path("encode").getTextValue())
					.setType(
							ParamType.valueOf(node.path("type").getTextValue()))
					.setAttack(node.path("attack").getBooleanValue())
					.setValue(node.path("value").getTextValue())
					.setClassify(node.path("classify").getTextValue())
					.setProject(node.path("project").getTextValue());

			result.add(param);
		}

		return result;
	}

	private PayloadSource readPayload(JsonNode json) {
		String payload = json.path("payloadString").getTextValue();

		if (isNumberPayloadSource(payload)) {
			return asNumberPayloadSource(payload);
		}

		return new StringListPayloadSource(payload);
	}

	private PayloadSource asNumberPayloadSource(String payload) {
		String numberDescr = StringUtils.substringAfter(payload, ":");
		String[] numbers = numberDescr.split(",");

		return new NumbersPayloadSource(Integer.parseInt(numbers[0]),
				Integer.parseInt(numbers[1]), Integer.parseInt(numbers[2]));
	}

	private boolean isNumberPayloadSource(String payload) {
		if (StringUtils.isBlank(payload)) {
			return false;
		}

		return payload.startsWith("numbers");
	}
}
